package com.kova.api.verify;

import com.alibaba.fastjson.JSON;
import com.kova.api.common.ResultEnum;
import com.kova.api.exception.CommonException;
import com.kova.api.utils.DateUtil;
import com.kova.api.utils.JwtUtils;
import com.kova.api.utils.Md5Util;
import com.kova.api.utils.StringUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;

@Component
@Aspect
public class ParameterVerifyAspect {


    /**
     * 签名加解密key配置
     */
    @Value("${sign.key}")
    private String sign_key;

    /**
     * 签名加解密key配置
     */
    @Value("${sign.secret}")
    private String sign_secret;


    //连接点（join point）: 连接点表示应用执行过程中能够插入切面的一个点，这个点可以是方法的调用、异常的抛出。在 Spring AOP 中，连接点总是方法的调用。
    @Before(value="@annotation(com.kova.api.verify.ParamsVerify)")
    public void aopParameterVerify(JoinPoint point) throws IllegalAccessException, NoSuchMethodException {

        //获取调用类
        Class clazz = point.getTarget().getClass();

        //获取方法名称
        String methodName = point.getSignature().getName();
        MethodSignature signature = (MethodSignature) point.getSignature();

        //获取调用方法对象
        Class[] parameterTypes = signature.getMethod().getParameterTypes();
        Method method = clazz.getMethod(methodName,parameterTypes);

        //获取注解类对象
        Annotation annotation=method.getAnnotation(ParamsVerify.class);
        if (annotation == null){
            //不需要验证
            return;
        }
        ParamsVerify  paramsVerify= (ParamsVerify)annotation;

        //获取当前http请求
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();


        //获取方法请求参数
        Map<String, Object>  params = getRequestParamsMap(point,method);


        //参数验证
        validateRequestParams(paramsVerify,request,params);

    }

    /**
     * 获取请求方法参数(如： public Result test(@RequestBody String body){}    则会获取参数名body与参数body值)
     * @param point
     * @param method
     * @return
     */
    private Map<String, Object> getRequestParamsMap(JoinPoint point,Method method) {
        // 方法参数值
        Object[] args = point.getArgs();
        ParameterNameDiscoverer pnd = new DefaultParameterNameDiscoverer();
        String[] parameterNames = pnd.getParameterNames(method);
        Map<String, Object> paramMap = new HashMap<>(32);
        for (int i = 0; i < parameterNames.length; i++) {
            paramMap.put(parameterNames[i], args[i]);
        }
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            Object itemValue = entry.getValue();
            if (itemValue instanceof Map){
                return (Map<String, Object>)itemValue;
            }else if(itemValue instanceof String){
               Map  map=JSON.parseObject(itemValue.toString(),Map.class);
               return map;
            }else {
                break;
            }
        }
        return paramMap;
    }

    /**
     * 方法参数验证
     * @param paramsVerify
     * @param request
     * @param params   方法参数
     */
    private void validateRequestParams(ParamsVerify  paramsVerify,HttpServletRequest request,Map<String, Object>  params){
        /************************************ 接口方法公有参数验证 start***********************************************/
        String  oncestr = params.get("oncestr")!=null? params.get("oncestr").toString().trim():"";
        if (StringUtils.isEmpty(oncestr)){
            throw new CommonException(ResultEnum.PARAM_ERROR.getCode(),"随机字符串参数oncestr不为空");
        }
        //时间戳(毫秒)
        String  timestamp = params.get("timestamp")!=null? params.get("timestamp").toString().trim():"";
        if (StringUtils.isEmpty(timestamp)){
            throw new CommonException(ResultEnum.PARAM_ERROR.getCode(),"时间戳(毫秒)参数timestamp不为空");
        }
        if (!StringUtil.isInteger(timestamp)){
            throw new CommonException(ResultEnum.PARAM_ERROR.getCode(),"时间戳(毫秒)参数timestamp格式错误");
        }
        //验证时间戳
        if (timestampVerify(timestamp)) {
            throw new CommonException(ResultEnum.INVALID_TIMESTAMP_EXPIRED.getCode(),"过期请求");
        }

        String  sign = params.get("sign")!=null? params.get("sign").toString().trim():"";
        if (StringUtils.isEmpty(sign)){
            throw new CommonException(ResultEnum.PARAM_ERROR.getCode(),"签名参数sign不为空");
        }
        //验证sign
        if (!verifySign(params)){
            throw new CommonException(ResultEnum.SIGN_ERROR.getCode(),"签名错误");
        }

        /************************************ 接口方法公有参数验证 end***********************************************/


        /************************************ 接口方法登录成功后验证 start***********************************************/
        if (paramsVerify.verifyToken()){ //验证用户
            //用户token
            String token=request.getHeader("token");
            JwtUtils.verifyToken(token);
        }
        /************************************ 接口方法登录成功后验证 end***********************************************/
    }

    //时间戳判断
    private  boolean timestampVerify(String timestamp) {

        String timeStr = DateUtil.stampToDate(timestamp, DateUtil.DATE_TIME_FMT);
        Date time1= DateUtil.parse(timeStr, DateUtil.DATE_TIME_FMT);
        Date time2= new Date();
        long  millisDiff=time2.getTime() - time1.getTime();
        if (millisDiff/(60 * 1000) > 2) { //超过2分钟则不可请求
            return true;
        }
        return false;
    }


    /**
     * sign验证   通过验证返回true,否则返回false
     * @param params  请求参数
     * @return     true:验证成功，false失败
     */
    private boolean verifySign(Map<String, Object> params) {

        String sign = params.get("sign").toString();

        String signStr = getParamsOrderStr(params);
        //log.info("组装字符串 = {}",signStr);
        String midleStr = sign_key + signStr+sign_secret;
        //log.info("签名字符串 = {}",midleStr);
        String localSign = Md5Util.getMD5Str(midleStr);
        //log.info("服务器md5签名 = {}",localSign);
        //log.info("用户md5签名 = {}",req.getSign());
        if (localSign.equals(sign)) {
            return true;
        }
        return false;
    }

    /**
     * 把数组所有元素排序，并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params
     * 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    private  String getParamsOrderStr(Map<String, Object> params) {

        Map<String, Object> rsParaMap = new HashMap<String, Object>();
        if (params!=null && !params.isEmpty()) {
            rsParaMap.putAll(params);
            if (rsParaMap.get("sign")!=null) {
                rsParaMap.remove("sign");
            }
        }

        List<String> keys = new ArrayList<String>(rsParaMap.keySet());
        Collections.sort(keys);
        String prestr = "";
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            Object value = rsParaMap.get(key);
            if (i == keys.size() - 1) {// 拼接时，不包括最后一个&字符
                prestr = prestr + key + "=" + value.toString();
            } else {
                prestr = prestr + key + "=" + value.toString() + "&";
            }
        }
        return prestr;
    }

}
